package cz.muni.fi.pa165.library.backend;

import cz.muni.fi.pa165.library.api.Available;
import cz.muni.fi.pa165.library.api.BookTO;
import cz.muni.fi.pa165.library.api.ReservationTO;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;

/**
 *
 * @author Filip Ligac
 */
@Repository
public class BorrowDAOImpl implements BorrowDAO {

    @PersistenceContext(name = "LibraryPU")
    private EntityManager entityManager;
    private ReservationDAO reservationDAO;

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    private void checkParameters(Borrow borrow) {
        if (borrow == null) {
            throw new IllegalArgumentException("Borrow cannot be null.");
        }
        if ((borrow.getReader() == null)) {
            throw new IllegalArgumentException("Borrow's reader ID cannot be null or negative.");
        }
        if (borrow.getTitles() == null) {
            throw new IllegalArgumentException("Borrow's title ID cannot be null.");
        }
        if (borrow.getBorrowDate() == null) {
            throw new IllegalArgumentException("Borrow's date cannot be null.");
        }
        if (borrow.getExpirationDate() == null) {
            throw new IllegalArgumentException("Borrow's expiration date cannot be null.");
        }
    }

    @Override
    public Borrow createBorrow(Borrow borrow) {
        checkParameters(borrow);
        reservationDAO = new ReservationDAOImpl();
        reservationDAO.setEntityManager(entityManager);

        if (borrow.getBorrowID() != null) {
            throw new IllegalArgumentException("Borrow id is already set.");
        }
        if (borrow.getTitles().size() > Borrow.MAXIMUM_BOOKS_COUNT_PER_BORROW) {
            throw new IllegalArgumentException("Cannot make borrow on more than 6 books.");
        }
        if (borrow.getTitles().isEmpty()) {
            throw new IllegalArgumentException("Cannot make borrow for no books.");
        }
        for (Book book : borrow.getTitles()) {
            if (book.getAvailability().equals(Available.BORROWED)) {
                throw new IllegalArgumentException("Cannot make borrow for borrowed books.");
            }

            if (book.getAvailability().equals(Available.RESERVED)) {
                Reservation reservation = reservationDAO.findReservationByBook(book);
                if (reservation == null) {
                    throw new IllegalStateException("There is no reservation for reserved book with id: " + book.getId());
                }
                if (!reservation.getReader().equals(borrow.getReader())) {
                    throw new IllegalArgumentException("Cannot make borrow for reserved books by another reader.");
                }
            }
        }

        for (Book book : borrow.getTitles()) {
            if (book.getAvailability().equals(Available.RESERVED)) {
                Reservation reservation = reservationDAO.findReservationByBook(book);
                reservationDAO.deleteReservation(reservation);
            }
            
            book.setAvailability(Available.BORROWED);
            entityManager.merge(book);
        }

        entityManager.persist(borrow);
        return borrow;
    }

    @Override
    public List<Borrow> findAllBorrows() {
        return entityManager.createQuery("SELECT e FROM Borrow e", Borrow.class).getResultList();
    }

    @Override
    public Borrow findBorrowById(Long borrowID) {
        if (borrowID == null) {
            throw new IllegalArgumentException("Id cannot be null.");
        }
        if (borrowID.intValue() <= 0) {
            throw new IllegalArgumentException("Id must be positive.");
        }

        Borrow borrow = entityManager.find(Borrow.class, borrowID);
        return borrow;
    }

    @Override
    public List<Borrow> findBorrowsByReader(Reader reader) {
        if (reader == null) {
            throw new IllegalArgumentException("Reader cannot be null.");
        }
        if (reader.getId() == null) {
            throw new IllegalArgumentException("Id cannot be null.");
        }
        if (reader.getId().intValue() <= 0) {
            throw new IllegalArgumentException("Id must be positive.");
        }

        String query = "SELECT e FROM Borrow e WHERE e.reader.id = " + reader.getId();
        return entityManager.createQuery(query, Borrow.class).getResultList();
    }

    @Override
    public Borrow findBorrowByTitle(Book title) {
        if (title == null) {
            throw new IllegalArgumentException("Title cannot be null.");
        }
        if (title.getId() == null) {
            throw new IllegalArgumentException("Id cannot be null.");
        }
        if (title.getId().intValue() <= 0) {
            throw new IllegalArgumentException("Id must be positive.");
        }

        Borrow borrow = new Borrow();

        List<Borrow> allBorrows = entityManager.createQuery("SELECT e FROM Borrow e", Borrow.class).getResultList();
        for (int i = 0; i < allBorrows.size(); i++) {
            for (int j = 0; j < allBorrows.get(i).getTitles().size(); j++) {
                if (allBorrows.get(i).getTitles().get(j).equals(title)) {
                    borrow = allBorrows.get(i);
                    break;
                }
            }
        }

        return borrow;
    }

    @Override
    public void updateExpirationDate(Borrow borrow, Date newExpirationDate) {

        if (borrow == null) {
            throw new IllegalArgumentException("Borrow cannot be null.");
        }
        if (newExpirationDate == null) {
            throw new IllegalArgumentException("Expiration date cannot be null.");
        }

        if (borrow != null) {
            borrow.setExpirationDate(newExpirationDate);
        } else {
            throw new IllegalArgumentException("Invalid borrow ID to change the expiration date.");
        }

        entityManager.merge(borrow);
    }

    @Override
    public Borrow returnBooks(Borrow borrow, List<Book> titles) {
        if (borrow == null) {
            throw new IllegalArgumentException("Borrow cannot be null.");
        }
        if ((titles == null) || (titles.size() <= 0)) {
            throw new IllegalArgumentException("Titles cannot be null.");
        }
        for (int i = 0; i < titles.size(); i++) {
            if (titles.get(i).getId() == null) {
                throw new IllegalArgumentException("Id cannot be null.");
            }
        }
        for (int i = 0; i < titles.size(); i++) {
            if (titles.get(i).getAvailability() != Available.BORROWED) {
                throw new IllegalArgumentException("Cannot return unborrowed books.");
            }
        }

        // Titles to remain in borrow
        List<Book> remainTitles = borrow.getTitles();

        // Find out which titles remain in borrow
        for (int i = 0; i < titles.size(); i++) {
            remainTitles.remove(entityManager.find(Book.class, titles.get(i).getId()));
            Book returnBook = entityManager.find(Book.class, titles.get(i).getId());
            returnBook.setAvailability(Available.AVAILABLE);
            entityManager.persist(returnBook);
        }

        // Set a new list of book in borrow
        borrow.setTitles(remainTitles);
        entityManager.merge(borrow);
        return borrow;
    }

    @Override
    public void borrowBooks(Borrow borrow, List<Book> titles) {
        if (borrow == null) {
            throw new IllegalArgumentException("Borrow cannot be null.");
        }
        if (titles == null) {
            throw new IllegalArgumentException("Titles cannot be null.");
        }
        if (titles.isEmpty()) {
            throw new IllegalArgumentException("Cannot borrow none book");
        }
        for (int i = 0; i < titles.size(); i++) {
            if (titles.get(i) == null) {
                throw new IllegalArgumentException("Book title cannot be null.");
            }
            if (titles.get(i).getAvailability().equals(Available.BORROWED)) {
                throw new IllegalArgumentException("Cannot borrow borrowed book");
            }
        }
        if ((borrow.getTitles().size() + titles.size() > Borrow.MAXIMUM_BOOKS_COUNT_PER_BORROW)) {
            throw new IllegalArgumentException("Cannot borrow more than 6 books.");
        }

        // Titles to be added to borrow
        List<Book> addTitles = borrow.getTitles();
        addTitles.addAll(titles);

        borrow.setTitles(addTitles);
        entityManager.merge(borrow);
    }

    @Override
    public boolean deleteBorrow(Borrow borrow) {
        if (borrow == null) {
            throw new IllegalArgumentException("Deleted borrow is null.");
        }
        if (borrow.getBorrowID() == null) {
            throw new IllegalArgumentException("Deleted borrow ID is negative.");
        }

        Borrow borrowEntity = entityManager.find(Borrow.class, borrow.getBorrowID());
        if (borrowEntity == null) {
            return false;
        }

        // Find out which titles remain in borrow
        for (int i = 0; i < borrow.getTitles().size(); i++) {
            Book book = entityManager.find(Book.class, borrow.getTitles().get(i).getId());
            book.setAvailability(Available.AVAILABLE);
            entityManager.persist(book);
        }

        entityManager.remove(borrowEntity);
        return true;
    }
}
